package task

import (
	"encoding/json"
	"github.com/astaxie/beego/logs"
	"github.com/astaxie/beego/orm"
	"io/ioutil"
	"net/http"
	lmodel "payspace/models"
	"strconv"
	"time"
)

const (
	baseUrl1 = "https://api.etherscan.io/api?module=transaction&action=gettxreceiptstatus&txhash=" // get transaction status api url (limit of 5 requests/sec)
	baseUrl2 = "https://api.blockcypher.com/v1/eth/main/txs/"                                      // get transaction by hash (limit of 3 requests/sec and 200 requests/hr)
	baseUrl3 = "https://api.blockcypher.com/v1/btc/main?token="
	apiToken = "d6d10fcde6a744a3ad87eb3b8859d4ef"
)

// update status
func updateStatus() {
	for {
		transactions, err := lmodel.GetUnstatusTx()
		if err != nil {
			time.Sleep(60 * time.Second)
			continue
		}

		for _, tx := range transactions {
			hash := tx.Hash
			statusUrl := baseUrl1 + hash

			i := 1
		RequestStatus:
			resp, err := http.Get(statusUrl)

			if err != nil {
				// handle error
				i++
				logs.Error("call api failed:", err)
				time.Sleep(60 * time.Second)
				if i > 5 {
					continue
				}
				goto RequestStatus
			}

			body, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				i++
				logs.Error("parse response failed:", err)
				time.Sleep(60 * time.Second)
				if i > 5 {
					continue
				}
				goto RequestStatus
			}

			ret := make(map[string]interface{})
			err = json.Unmarshal(body, &ret)
			if err != nil {
				i++
				logs.Error("parse response failed:", err)
				time.Sleep(60 * time.Second)
				if i > 5 {
					continue
				}
				goto RequestStatus
			}

			resp.Body.Close()

			if ret == nil {
				logs.Error("transaction [%v]: status date error", hash)
				continue
			}

			status := ret["result"].(map[string]interface{})

			// 更新数据
			o := orm.NewOrm()
			transaction := lmodel.Transaction{Hash: hash}
			err = o.Read(&transaction, "Hash")
			transaction.Status = status["status"].(string)
			if _, err := o.Update(&transaction, "Status"); err != nil {
				logs.Error("Update transaction status failed: ", err)
				continue
			}
			time.Sleep(1 * time.Second)
		}

		time.Sleep(30 * time.Second)

	}
}

// update transaction
func updateTransaction() {
	// add api token
	tokenUrl := baseUrl3 + apiToken
	response, err := http.Get(tokenUrl)

	if err != nil {
		// handle error
		logs.Error("add api token failed:", err)
	}
	response.Body.Close()

	for {
		transactions, err := lmodel.GetSuccessTx()
		//transactions, err := lmodel.GetUnconfirmedTx()
		if err != nil {
			time.Sleep(60 * time.Second)
			continue
		}

		for _, tx := range transactions {
			hash := tx.Hash
			confirmations := tx.Confirmations
			if confirmations != "" {
				if confirmationsInt, err := strconv.Atoi(confirmations); err == nil && confirmationsInt > 20 {
					continue // do not update tx when confirmations > 20
				}
			}

			statusUrl := baseUrl2 + hash
			logs.Debug("visit:", statusUrl) // 测试打印

			i := 1
		GetTxDetail:
			resp, err := http.Get(statusUrl)

			if err != nil {
				// handle error
				i++
				logs.Error("call api failed:", err)
				time.Sleep(60 * time.Second)
				if i > 5 {
					continue
				}
				goto GetTxDetail
			}

			body, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				i++
				logs.Error("parse response failed:", err)
				time.Sleep(20 * time.Second)
				if i > 5 {
					continue
				}
				goto GetTxDetail
			}

			ret := make(map[string]interface{})
			err = json.Unmarshal(body, &ret)
			if err != nil {
				i++
				logs.Error("parse response failed:", err)
				time.Sleep(20 * time.Second)
				if i > 5 {
					continue
				}
				goto GetTxDetail
			}
			logs.Debug("content:=====>", ret) // 测试打印

			resp.Body.Close()

			// 错误数据
			if _, ok := ret["error"]; ok {
				logs.Error("this transaction not available")
				continue
			}

			if ret["block_height"].(float64) == -1 {
				logs.Debug("this transaction is pending: ", hash)
				continue
			}

			// 更新数据
			o := orm.NewOrm()
			transaction := lmodel.Transaction{Hash: hash}
			err = o.Read(&transaction, "Hash")
			transaction.BlockHash = ret["block_hash"].(string)
			transaction.BlockNumber = strconv.FormatFloat(ret["block_height"].(float64), 'f', -1, 64)
			transaction.Confirmations = strconv.FormatFloat(ret["confirmations"].(float64), 'f', -1, 64)
			transaction.GasUsed = strconv.FormatFloat(ret["gas_used"].(float64), 'f', -1, 64)
			if _, err := o.Update(&transaction, "BlockHash", "BlockNumber", "Confirmations", "GasUsed"); err != nil {
				logs.Error("Update transaction status failed: ", err)
				continue
			}
			logs.Info("transaction updated.")
			time.Sleep(20 * time.Second)
		}
		time.Sleep(60 * time.Second)
	}
}
